-module(ring).
-export([create/1,stop/1,init/1,test/0,ecrire/0,envoyer_ecrire/4,arreter/0,lookup/2,get/3,put/3,chercher/1,envoyer/2,recuperer/1]).

% bin_to_hex_list(Bin) when is_binary(Bin) ->
%     lists:flatten([integer_to_list(X,16) || <<X>> <= Bin]).
%fonction de recherche du processus responsable d'une clé donnée
lookup(CleHachee,{Nom,MaCle,Next,CleSuivant}) ->
%io:format("A]~w~n B]~w~n C]~w~n",[MaCle,CleHachee,CleSuivant]),
if
    ((MaCle =< CleHachee ) and (CleHachee < CleSuivant)) ->
    Next;
    true ->
    Next ! {lookup,CleHachee,Nom,0},
    receive
        {ok_lookup,Hote} ->
        Hote;
        {lookup,_,Who,0} ->
        Who ! {tour_lookup},
        receive
            {ok_lookup,Hote} ->
            Hote
        end;
        {tour_lookup}->
        Nom
    end
end.
%Fonction permettant à tout processus de retrouver une donnée à partir de sa clé 
get(CleHachee,Who,{Nom,Cle,Next,CleSuivant}) ->
Hote = lookup(CleHachee,{Nom,Cle,Next,CleSuivant}),
Hote ! {getdata,CleHachee,Who,0}.

%stocker une donnée dans la table
put(CleHachee,Data) ->
    Processus = lookup(CleHachee),
    Processus ! {put,Data,self()}.

put(CleHachee,Data,Moi) ->
Processus = lookup(CleHachee,Moi),
Processus ! {putdata,self(),CleHachee,Data,0}.

putData(Map,Cle,Data)->
Map2 = dict:store(Cle,Data,Map),
Map2.

relier(X,Y) ->
X ! {next,self(),1},
receive {NX,_} ->
    Y ! {next,self(),1},
    receive {NY,_} ->
        Y ! {set,NX,self(),1},
        X ! {set,NY,self(),1},
        receive {ok,_} -> {} end,
        receive {ok,_} -> {} end
    end
end.

wait(Next,Node,T) ->
{Cle,{Nom,Noeud,Map}} = Node,
CleSuivant = crypto:sha(term_to_binary(Next)),%io:format("wait ~w~n",[Nom]),

receive
    {next,Who,TT} ->
    NT = max(T,TT)+1,
    Who ! {Next,NT},
    envoyer_ecrire(NT,Node,'next',Who),
    wait(Next,Node,NT);

    {set,NNext,Who,TT} -> 
    NT = max(T,TT)+1,
    Who ! {ok,NT},
    envoyer_ecrire(NT,Node,'set',Who),
    wait(NNext,Node,NT); 

    {nom, Who,TT} -> 
    NT = max(T,TT)+1,
    Who ! {Nom,NT},
    envoyer_ecrire(NT,Node,'nom',Who),
    wait(Next,Node,NT);

    {lookup,CleHachee,Who,TT} ->
    NT = max(T,TT)+1,
    Hote = lookup(CleHachee,{Nom,Cle,Next,CleSuivant}),
    Who ! {ok_lookup,Hote},
    wait(Next,Node,NT);

    {get,CleHachee,Who,TT} -> 
    NT = max(T,TT)+1,
    get(CleHachee,Who,{Nom,Cle,Next,CleSuivant}),
    wait(Next,Node,NT);

    {getdata,CleHachee,Who,TT} -> 
    NT = max(T,TT)+1,
    Data = getData(Map,CleHachee),
    Who ! {ok_get,Data},
    envoyer_ecrire(NT,Node,'getdata',Who),
    wait(Next,Node,NT);

    {put,CleHachee,Data,Who,TT} ->
    NT = max(T,TT)+1,
    Hote = lookup(CleHachee,{Nom,Cle,Next,CleSuivant}),
    Hote ! {putdata,CleHachee,Data,Who,NT},
    envoyer_ecrire(NT,Node,'putData',Hote),
    wait(Next,Node,NT);

    {putdata,CleHachee,Data,Who,TT} ->
    NT = max(T,TT)+1,
    Map2 = putData(Map,CleHachee,Data),
    Who ! {ok_put},
    NNode = {Cle,{Nom,Noeud,Map2}},
    wait(Next,NNode,NT);

    {cle,Who,TT} -> 
    NT = max(T,TT)+1,
    Who ! {Cle,NT},
    envoyer_ecrire(NT,Node,'cle',Who),
    wait(Next,Node,NT);  

    {cleSuivant, Who,TT} -> 
    NT = max(T,TT)+1,
    Who ! {CleSuivant,self(),NT},
    envoyer_ecrire(NT,Node,'cleSuivant',Who),
    wait(Next,Node,NT);

    stop -> {}
end.

envoyer_ecrire(Temps,Node,Message,Dest) -> 
{Cle,{Nom,_,_}} = Node,
CleSuivant = crypto:sha(term_to_binary(Dest)),
ecrivain ! {Temps,{Nom,Cle},Message,{Dest,CleSuivant}}.

ecrire()->
receive
    {Temps,{Pr1,Hach1},Message,{Pr2,Hach2}} ->
    file:write_file("log.txt", io_lib:fwrite("~w) ~w]~n <~w,~w> ~n --~w--> ~n <~w,~w> ~n~n", [date(),Temps,Pr1,Hach1,Message,Pr2,Hach2]),[append]),
    ecrire();
    stop_ecrire -> {}
end.

creer_ecrivain() ->
Pid = spawn(?MODULE,ecrire,[]),
register(ecrivain, Pid).
% H = dict:new(). 
%{dict,0,16,16,8,80,48,
%      {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
%      {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}
%27> H1=dict:store(poule,110,H).
%{dict,1,16,16,8,80,48,
%      {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
%      {{[],[],[],[],[],
%        [[poule|110]],
%        [],[],[],[],[],[],[],[],[],[]}}}
%28> 
%28> dict:fetch(poule,H1).
create(L) ->
creer_ecrivain(),
N = lists:map(fun(X)-> {crypto:sha(term_to_binary(X)),{X,node(),dict:new()}} end, L),
NT = lists:keysort(1,N),
NO = lists:map(fun({_,{Nom,Noeud,Map}}) -> {Nom,Noeud,Map} end, NT),
{_,[Tete|_]} = pop(NT),

afficher(NO),
{Tete}.

stop([]) -> 
ecrivain ! stop_ecrire;
stop([E|R]) ->
E ! stop,
stop(R).

init(Node) ->
{_,{Nom,_,_}} = Node,
wait(Nom,Node,0).

popNode(Node) ->
{_,{Nom,_,_}} = Node,
Pid = spawn(?MODULE,init,[Node]),
register(Nom, Pid),
{Nom,[Nom]}.

pop([Node|[]]) ->
{P,A} = popNode(Node),
{P,A};
pop([Node|R]) ->
{P,A} = popNode(Node),
{Q,B} = pop(R),
relier(P,Q),
{Q, A++B}
.

afficher({_,{Nom,Node,_}}) ->
io:format("Node{Nom(~w) Node(~w) Cle(...)} ~n",[Nom,Node]);
afficher({Nom,Node,_}) ->
io:format("Node{Nom(~w) Node(~w)} ~n",[Nom,Node]);
afficher([]) -> {};
afficher([Node|R]) ->
afficher(Node),
afficher(R).

test() ->
create([toto,tata,bob,anna,lea]),
envoyer(toto,abcdef),
recuperer(toto).

arreter() ->
stop([toto,tata,bob,anna,lea]).

chercher(Nom) ->
bob ! {lookup,crypto:sha(term_to_binary(Nom)),self(),0},
receive
    {ok_lookup,Hote} ->
    Hote
end.

envoyer(Nom,Data) ->
bob ! {put,crypto:sha(term_to_binary(Nom)),Data,self(),0},
receive
    {ok_put} ->
    ok
end.

recuperer(Nom) ->
bob ! {get,crypto:sha(term_to_binary(Nom)),self(),0},
receive
    {ok_get,Data} ->
    Data
end.